package linq;

import haxe.FastList;
import linq.exception.InvalidOperationException;

import haxe.macro.Expr;

class Linq { //implements haxe.rtti.Generic {
	#if !debug inline #end 
	public static function Where<T> (source:Enumerable<T>, predicate:T->Bool) {
		var out = new LinqIterable<T> ();
		for (elem in source) if (predicate (elem)) out.add (elem);
		return out;
	}

	#if !debug inline #end 
	public static function WhereI<T> (source:Enumerable<T>, predicate:T->Int->Bool) {
		var out = new LinqIterable<T> ();
		var i:Int = 0;
		for (elem in source) if (predicate (elem, i++)) out.add (elem);
		return out;
	}

	#if !debug inline #end 
	public static function ToArray<T> (source:Enumerable<T>):Array<T> {
		var out = [];
		for (elem in source) out.push (elem);
		return out;
	}

	#if !debug inline #end 
	public static function ToList<T> (source:Enumerable<T>):List<T> {
		var out = new List<T> ();
		for (elem in source) out.push (elem);
		return out;
	}

	#if !debug inline #end 
	public static function Select<T,K> (source:Enumerable<T>, predicate:T->K) {
		var out = new LinqIterable<K> ();
		for (elem in source) out.add (predicate (elem));
		return out;
	}

	#if !debug inline #end 
	public static function SelectI<T,K> (source:Enumerable<T>, predicate:T->Int->K) {
		var out = new LinqIterable<K> ();
		var ct:Int = 0;
		for (elem in source) out.add (predicate (elem, ct++));
		return out;
	}

	#if !debug inline #end 
	public static function Skip<T> (source:Enumerable<T>, count:Int) {
		var out = new LinqIterable<T> ();
		var ct:Int = 0;
		for (elem in source) if (++ct > count) out.add (elem);
		return out;
	}

	#if !debug inline #end 
	public static function First<T> (source:Enumerable<T>) {
		var elem:T = null;
		for (item in source) {
			elem = item;
		}
		throw new InvalidOperationException ("Sequence contains no elements");
		return elem;
	}

	#if !debug inline #end 
	public static function FirstWhere<T> (source:Enumerable<T>, predicate:T->Bool) {
		for (elem in source) if (predicate (elem)) return elem;
		throw new InvalidOperationException ("Sequence contains no elements");
	}

	/* Holding off on this one for just a bit. (Needs macro)
	#if !debug inline #end 
	public static function FirstOrDefault<T> (source:Enumerable<T>) {
		for (elem in source) return elem;
		trace (typeof (T));
	}
	*/

	#if !debug inline #end 
	public static function Single<T> (source:Enumerable<T>) {
		var out:T = null;
		var ct:Int = 0;
		for (elem in source){
			ct++;
			if (elem == null) 
				out = elem;
			else throw new InvalidOperationException ("The input sequence contains more than one element");
		}
		if (ct == 0) throw new InvalidOperationException ("The input sequence is empty");
		return out;
	}

	#if !debug inline #end 
	public static function SingleWhere<T> (source:Enumerable<T>, predicate:T->Bool) {
		var out:T = null;
		var ct:Int = 0;
		for (elem in source){
			ct++;
			if (predicate (elem)) if (elem == null) 
				out = elem;
			else throw new InvalidOperationException ("More than one element satisfies the condition in predicate");
		}
		if (ct == 0) throw new InvalidOperationException ("The input sequence is empty");
		if (out == null) throw new InvalidOperationException ("No element satisfies the condition in predicate");
		return out;
	}

	#if !debug inline #end 
	public static function Take<T> (source:Enumerable<T>, count:Int) {
		var out = new LinqIterable<T> ();
		var ct:Int = 0;
		for (elem in source) if (ct++ < count) out.add (elem);
		return out;
	}

	#if !debug inline #end 
	public static function TakeWhile<T> (source:Enumerable<T>, predicate:T->Bool) {
		var out = new LinqIterable<T> ();
		for (elem in source) if (predicate (elem)) out.add (elem) else break;
		return out;
	}

	#if !debug inline #end 
	public static function Any<T> (source:Enumerable<T>) {
		var out = null;
		for (elem in source) out = elem;
		return (out!=null);
	}

	#if !debug inline #end 
	public static function AnyWhere<T> (source:Enumerable<T>, predicate:T->Bool) {
		for (elem in source) if (predicate (elem)) return true;
		return false;
	}

	#if !debug inline #end 
	public static function Count<T> (source:Enumerable<T>) {
		var ct:Int = 0;
		for (elem in source) ct++;
		return ct;
	}

	#if !debug inline #end 
	public static function CountWhere<T> (source:Enumerable<T>, predicate:T->Bool) {
		var ct:Int = 0;
		for (elem in source) if (predicate (elem)) ct++;
		return ct;
	}

	//SingleOrDefault, Holding off for a while, this needs a macro

	//OrderBy
	/*
	#if !debug inline #end 
	public static function OrderBy<T,K> (source:Enumerable<T>, keySelector:T->K) {
		var arr = ToArray (source);
		arr.sort (predicate (arr))
	}
	*/
	//OrderByDescending

	#if !debug inline #end 
	public static function Print (source:Dynamic) {
		trace (source);
	}
}

typedef Enumerable<T> = Iterable<T>;

typedef LinqIterable<T> = List<T>;